package com.hospital.wx.service.Impl;

import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.IdUtil;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.hospital.wx.dao.AppointmentOrderDao;
import com.hospital.wx.dao.DoctorWorkPlanDao;
import com.hospital.wx.dao.DoctorWorkPlanScheduleDao;
import com.hospital.wx.dao.DoctorsDao;
import com.hospital.wx.service.AppointmentOrderService;
import com.hospital.wx.util.Constants;
import com.hospital.wx.util.SnowflakeIdGenerator;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

@Service
@Slf4j
public class AppointmentOrderServiceImpl implements AppointmentOrderService {
    @Autowired
    private AppointmentOrderDao appointmentOrderDao;

    @Autowired
    private DoctorWorkPlanDao doctorWorkPlanDao;

    @Autowired
    private DoctorWorkPlanScheduleDao doctorWorkPlanScheduleDao;

    @Autowired
    private DoctorsDao doctorsDao;

    @Autowired
    private RedisTemplate redisTemplate;

    private String WORK_PLAN_SCHEDULE_KEY = "work_plan_schedule_";

    private String REGISTRATION_PAYMENT_ = "registration_payment_";

    private String DOCTOR_SCHEDULE_KEY = "doctor_schedule_";

    /*
    * 挂号的业务逻辑(这里是创建订单的业务逻辑)，一样的
    * */
    @Override
    public Map createAppointmentOrder(Map param) {
        int workPlanId = MapUtil.getInt(param, "workPlanId");
        int scheduleId = MapUtil.getInt(param, "scheduleId");

        //检查Redis中是否存在日程缓存（过期的出诊计划和时段会自动删除），不存在缓存就不执行挂号
        String key = DOCTOR_SCHEDULE_KEY + scheduleId;
        if (!redisTemplate.hasKey(key)) {
            return null;
        }

        //占用 Redis 中的挂号名额*********这里就是利用Redis的乐观锁机制来进行避免超售
        if (!checkAndReserveScheduleSlot(key)) {
            return null;
        }

        try {
            //1.创建支付订单
            SnowflakeIdGenerator idGenerator = new SnowflakeIdGenerator(1, 1);
            long orderId = idGenerator.nextId();
            String formattedOrderId = "" + orderId;
            log.info("格式化后的订单 ID: {}", formattedOrderId);
            param.put("orderId", formattedOrderId);
            formattedOrderId = "ORD" + orderId;
            param.put("orderNo", formattedOrderId);
            //流水号
            String outTradeNo = IdUtil.simpleUUID().toUpperCase();
            param.put("outTradeNo", outTradeNo);

            Integer doctorId = MapUtil.getInt(param, "doctorId");
            String deptName = doctorsDao.selectDeptNameById(doctorId);
            param.put("deptName", deptName);

            //订单表中添加一条数据 2.保存挂号记录
            appointmentOrderDao.createAppointmentOrder(param);

            // 3.更新医生出诊计划与时段的实际挂号人数
            updateAppointmentCount(workPlanId, scheduleId);

            //4.在Redis中缓存付款记录，并设置过期时间
            cachePaymentInfo(outTradeNo, workPlanId, scheduleId);

            // 获取当前时间
            LocalDateTime currentDateTime = LocalDateTime.now();
            // 创建 DateTimeFormatter 对象，指定日期格式
            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
            // 格式化日期
            String formattedDateTime = currentDateTime.format(formatter);
            param.put("createTime", formattedDateTime);
            param.put("status", 0);
            return param;   //返回给前端的数据
        } catch (Exception e) {
            // 记录错误日志，包含异常信息和关键参数
            log.error("微信支付挂号异常， 错误信息: {}", e.getMessage(), e);
            if (redisTemplate.hasKey(key)) {
                //恢复缓存该日程已经挂号数量
                redisTemplate.opsForHash().increment(key, "num", -1);
            }
            // 回滚医生排班的预约名额
            rollbackScheduleSlot(key);
            throw e;
        }
    }


    /**
     * 占用 Redis 中的挂号名额
     */
    private boolean checkAndReserveScheduleSlot(String key) {
        // 使用 Redis 事务执行操作
        Object execute = redisTemplate.execute(new SessionCallback<Object>() {
            @Override
            public Object execute(RedisOperations operations) {
                // 监听指定的键，以检测是否被其他事务修改
                operations.watch(key);
                // 获取 Redis 哈希结构中的所有键值对
                Map<Object, Object> entry = operations.opsForHash().entries(key);
                // 获取最大可预约数量（默认值为 0）
                int maximum = MapUtil.getInt(entry, "maximum", 0);
                // 获取当前已预约数量（默认值为 0）
                int num = MapUtil.getInt(entry, "num", 0);
                // 如果已预约数量达到或超过最大限制，则取消监视并返回 null
                if (num >= maximum) {
                    operations.unwatch();
                    return null;
                }
                // 开启 Redis 事务
                operations.multi();
                // 在哈希表中将 "num" 的值加 1，表示新增一个预约
                operations.opsForHash().increment(key, "num", 1L);
                // 提交事务，如果 `watch` 监视的键未被修改，则执行操作，否则事务回滚
                return operations.exec();
            }
        });
        // 如果事务执行成功，则返回 true，否则返回 false
        return execute != null;
    }

    /**
     * 创建支付订单
     *
     */
//    private ObjectNode createPaymentOrder(String outTradeNo, String openId, int total) {
//        //学习过程中测试代码调用mockCreatePaymentOrder，实际开发调用createPaymentOrder
//        return paymentService.mockCreatePaymentOrder(outTradeNo, openId, total, "挂号费", notifyUrl, null);
//    }

    /**
     * 更新出诊计划与时段的实际挂号人数
     */
    private void updateAppointmentCount(int workPlanId, int scheduleId) {
        doctorWorkPlanDao.updateNumById(Map.of("id", workPlanId, "n", 1));
        doctorWorkPlanScheduleDao.updateNumById(Map.of("id", scheduleId, "n", 1));
    }

    /**
     * 缓存付款信息
     */
    private void cachePaymentInfo(String outTradeNo, int workPlanId, int scheduleId) {
        String redisKey = REGISTRATION_PAYMENT_ + outTradeNo;
        redisTemplate.opsForHash().putAll(redisKey, Map.of(
                "workPlanId", workPlanId,
                "scheduleId", scheduleId,
                "outTradeNo", outTradeNo
        ));
        //实际开发
//        redisTemplate.expireAt(redisKey, new DateTime().offset(DateField.MINUTE, 6));
        //测试代码
        redisTemplate.expireAt(redisKey, new DateTime().offset(DateField.MINUTE, 10));

    }


    /**
     * 事务失败时回滚 Redis 挂号名额
     */
    private void rollbackScheduleSlot(String key) {
        if (Boolean.TRUE.equals(redisTemplate.hasKey(key))) {
            redisTemplate.opsForHash().increment(key, "num", -1);
        }
    }

    @Override
    public HashMap selectOrderDetailById(String orderId) {
        return appointmentOrderDao.selectOrderDetailById(orderId);
    }

    /*
    * 支付成功后跟新status和redis缓存
    * */
    @Override
    @Transactional
    public int updateOrderStatus(Map param) {
        //查询outTradeNo
        String orderId = MapUtil.getStr(param, "orderId");
        String outTradeNo = appointmentOrderDao.selectOutTradeNoByOrderId(orderId);
        //更新 Redis 缓存中的已挂号人数
        HashMap<String, Integer> map = appointmentOrderDao.getDoctorScheduleIdByOutTradeNo(outTradeNo);
        if (map != null) {
            // 更新 Redis 缓存中的已挂号人数
            updateRedisCache(map.get("doctorScheduleId"));
        }
        //删除redis缓存
        deleteRedisCache(outTradeNo);

        return appointmentOrderDao.updateOrderStatus(param);
    }

    /*
     * 取消订单后跟新status和redis缓存
     * */
    @Override
    public int cancelOrder(String orderId) {
        //查询outTradeNo
        String outTradeNo = appointmentOrderDao.selectOutTradeNoByOrderId(orderId);
        //更新 Redis 缓存中的已挂号人数
        HashMap<String, Integer> map = appointmentOrderDao.getDoctorScheduleIdByOutTradeNo(outTradeNo);
        if (map != null) {
            // 更新 Redis 缓存中的已挂号人数
            updateRedisCache02(map.get("doctorScheduleId"));
        }
        //删除缓存
        deleteRedisCache(outTradeNo);
        //扣减库存(挂号人数)
        doctorWorkPlanDao.releaseRegistrationLock(outTradeNo);
        doctorWorkPlanScheduleDao.releaseRegistrationLock(outTradeNo);
        return appointmentOrderDao.cancelOrder(orderId);
    }

    /**
     * 更新 Redis 缓存中的已挂号人数********* +1
     */
    private void updateRedisCache(int scheduleId) {
        String key = Constants.WORK_PLAN_SCHEDULE_KEY + scheduleId;
        try {
            redisTemplate.opsForHash().increment(key, "num", 1);
        } catch (Exception e) {
            log.warn("Redis 更新失败，Key：{}", key, e);
        }
    }

    /**
     * 更新 Redis 缓存中的已挂号人数****** -1
     */
    private void updateRedisCache02(int scheduleId) {
        String key1 = Constants.DOCTOR_ONLINE_SUCCESS + scheduleId;
        try {
            redisTemplate.opsForHash().increment(key1, "num", -1);
        } catch (Exception e) {
            log.warn("Redis 更新失败，Key：{}", key1, e);
        }
    }

    /*
    * 删除redis中的对应的key缓存记录
    * */
    public void deleteRedisCache(String outTradeNo) {
        String redisKey = REGISTRATION_PAYMENT_ + outTradeNo;
        log.info("--------------------------redisKey:{}————————————————————————————————————————", redisKey);
        if (redisTemplate.hasKey(redisKey)) {
            redisTemplate.delete(redisKey);
        }
    }


    @Override
    public ArrayList<HashMap> getOrderList(Integer patientId) {
        return appointmentOrderDao.getOrderList(patientId);
    }
}

